﻿

using System;
using System.Collections.Generic;
using System.Linq;
using Bots.DungeonBuddy.Helpers;
using CommonBehaviors.Actions;
using FarmerJoe.Helpers.Logging;
using Styx;
using Styx.Common;
using Styx.CommonBot;
using Styx.TreeSharp;
using Styx.WoWInternals;
using Styx.WoWInternals.WoWObjects;
using Action = Styx.TreeSharp.Action;



namespace FarmerJoe
{
    namespace Helpers
    {
        public delegate WoWUnit UnitSelectionDelegate(object context);
        public delegate bool SimpleBooleanDelegate(object context);
        public delegate WoWPoint LocationRetriever(object context);

        public static class Spells
        {

            private static LocalPlayer Me { get { return StyxWoW.Me; } }
            private static WoWUnit CT { get { return Me.CurrentTarget; } }
            private const int SpellClipMs = 150;
            public static string LastSpellCast { get; set; }

            #region Supporting Stuff

            public static void LUACast(string spellName)
            {
                Log.Write("[LUA] Casting " + spellName);
                Lua.DoString(string.Format("RunMacroText(\"/cast {0}\")", spellName));
            }

            public static Composite PreventDoubleCast(params string[] spellNames)
            {
                return PreventDoubleCast(ret => Me.CurrentTarget, spellNames);
            }

            public static Composite PreventDoubleCast(UnitSelectionDelegate unit, params string[] spellNames)
            {
                return
                    new PrioritySelector(
                        new Decorator( ret =>
                            StyxWoW.Me.IsCasting && spellNames.Contains(StyxWoW.Me.CastingSpell.Name) 
                                && unit != null && unit(ret) != null 
                                && unit(ret).Auras.Any(a => a.Value.SpellId == StyxWoW.Me.CastingSpellId 
                                && a.Value.CreatorGuid == StyxWoW.Me.Guid),
                            new Action(ret => SpellManager.StopCasting())));
            }

            public static bool HasSpell(string spellName)
            {
                if (String.IsNullOrEmpty(spellName))return false;
                return SpellManager.HasSpell(spellName);
            }

            public static bool HasSpell(int spellID)
            {
                return SpellManager.HasSpell(spellID);
            }

            /// <summary>
            /// Returns TRUE if ANY of the IDs exist in the spell book
            /// </summary>
            /// <param name="listOfSpellIDs"></param>
            /// <returns></returns>
            public static bool HasSpell(List<int> listOfSpellIDs )
            {
                foreach (int spellID in listOfSpellIDs)
                {
                    if (SpellManager.HasSpell(spellID)) return true;
                }
                return false;
            }

            public static bool HasSpell(List<string> listOfSpells)
            {
                foreach (string name in listOfSpells)
                {
                    if (SpellManager.HasSpell(name)) return true;
                }
                return false;
            }


            public static Composite WaitForCast()
            {
                return new Action(
                    ret =>
                        {
                            bool allowLagTollerance = ClassParser.AllowLagTollerance;

                            if (!StyxWoW.Me.IsCasting && !SpellManager.GlobalCooldown) return RunStatus.Failure;

                            if (StyxWoW.Me.IsWanding()) return RunStatus.Failure;

                            // Shouldn't this return SUCCESS???
                            if (StyxWoW.Me.ChannelObjectGuid > 0) return RunStatus.Failure;

                            uint latency = StyxWoW.WoWClient.Latency*2;
                            TimeSpan castTimeLeft = StyxWoW.Me.CurrentCastTimeLeft;

                            if (allowLagTollerance)
                            {
                                if (SpellManager.GlobalCooldown && SpellManager.GlobalCooldownLeft.TotalMilliseconds < (latency)) return RunStatus.Failure;
                                if (castTimeLeft != TimeSpan.Zero && StyxWoW.Me.CurrentCastTimeLeft.TotalMilliseconds < (latency)) return RunStatus.Failure;
                            }

                            return RunStatus.Success;
                        });
            }

            public static Composite WaitForLagDuration()
            {
                return new WaitContinue(TimeSpan.FromMilliseconds((StyxWoW.WoWClient.Latency * 2) + 150), ret => false, new ActionAlwaysSucceed());
            }

            public static Composite Dismount(string reason)
            {
                return new Sequence(
                    // Write to the log file WHY we're dismounting
                    new Action(ret => Logger.WriteDebug(Styx.Resources.StyxResources.Stop_and_dismount___ + (!string.IsNullOrEmpty(reason) ? (" Reason: " + reason) : string.Empty))),
                    
                    // stop moving 
                    new DecoratorContinue(ret => StyxWoW.Me.IsMoving,
                                          new Sequence(
                                              new Action(ret => WoWMovement.MoveStop()), WaitForLagDuration())), 
                        
                    // Land if we're flying
                    new DecoratorContinue(ret => StyxWoW.Me.IsFlying,
                                          new Sequence(
                                              new Action(ret => WoWMovement.Move(WoWMovement.MovementDirection.Descend)),
                                              new WaitContinue(30, ret => !StyxWoW.Me.IsFlying, new ActionAlwaysSucceed()),
                                              new Action(ret => WoWMovement.MoveStop(WoWMovement.MovementDirection.Descend)))),
                    
                    // and finally dismount. 
                    new Action(r =>
                                   {
                                       ShapeshiftForm shapeshift = StyxWoW.Me.Shapeshift;
                                       if ((shapeshift != ShapeshiftForm.FlightForm) && (shapeshift != ShapeshiftForm.EpicFlightForm)) Lua.DoString("Dismount()");
                                       else Lua.DoString("RunMacroText('/cancelform')");
                                   }));
            }

            public static float MeleeRange
            {
                get
                {
                    if (!StyxWoW.Me.GotTarget)return 0f;
                    if (StyxWoW.Me.CurrentTarget.IsPlayer)return 3.5f;

                    return Math.Max(5f, StyxWoW.Me.CombatReach + 1.3333334f + StyxWoW.Me.CurrentTarget.CombatReach);
                }
            }

            public static readonly Dictionary<string, DateTime> DoubleCastPreventionDict = new Dictionary<string, DateTime>();
            private static void UpdateDoubleCastDict(string spellName)
            {
                if (DoubleCastPreventionDict.ContainsKey(spellName)) DoubleCastPreventionDict[spellName] = DateTime.UtcNow;

                DoubleCastPreventionDict.Add(spellName, DateTime.UtcNow);
            }
            #endregion

            #region Healing

            public static Composite Resurrect(string spellName)
            {
                return
                    new PrioritySelector(ctx => Group.ResurrectablePlayers.FirstOrDefault(u => !Blacklist.Contains(u)),
                                         new Decorator(ctx => ctx != null && !Battlegrounds.IsInsideBattleground,
                                                       new Sequence(Cast(spellName, ctx => (WoWPlayer) ctx),
                                                                    new Action( ctx => Blacklist.Add((WoWPlayer) ctx, TimeSpan.FromSeconds(30))))));
            }


            public static Composite Heal(string spellName, SimpleBooleanDelegate requirements)
            {
                return Heal(spellName, ret=> Me, requirements);
            }

            public static Composite Heal(string spellName, UnitSelectionDelegate targetUnit, SimpleBooleanDelegate requirements)
            {
                return new Sequence(
                    Cast(spellName,requirements,targetUnit),

                    // Little bit wait here to catch casting
                    new WaitContinue(1, ret =>
                    {
                        WoWSpell spell;
                        if (SpellManager.Spells.TryGetValue(spellName, out spell))
                        {
                            return spell.CastTime == 0 || StyxWoW.Me.IsCasting;
                        }

                        return true;
                    }, new ActionAlwaysSucceed()), new WaitContinue(10, ret =>
                    {
                        // Let channeled heals been cast till end.
                        if (StyxWoW.Me.ChanneledSpell != null) 
                        {
                            return false;
                        }

                        // Interrupted or finished casting. Continue
                        if (!StyxWoW.Me.IsCasting)
                        {
                            return true;
                        }

                        // If requirements don't meet anymore, stop casting and let it continue
                        if (!requirements(ret))
                        {
                            SpellManager.StopCasting();
                            return true;
                        }
                        return false;
                    }, new ActionAlwaysSucceed()));
            }
            #endregion

            #region Buffs & Debuffs

            // Debuff
            public static Composite Debuff(string spellName, bool checkHasMyAura, params string[] buffNames)
            {
                return Debuff(spellName, null, checkHasMyAura, buffNames);
            }

            public static Composite Debuff(string spellName, SimpleBooleanDelegate requirements, bool checkHasMyAura, params string[] buffNames)
            {
                return Buff(spellName, ret => StyxWoW.Me.CurrentTarget, requirements, checkHasMyAura, buffNames);
            }

            public static Composite Debuff(string spellName, SimpleBooleanDelegate requirements)
            {
                return Buff(spellName, ret => StyxWoW.Me.CurrentTarget, requirements,true);
            }

            // Buff Self
            public static Composite BuffSelf(string name)
            {
                return Buff(name, ret => StyxWoW.Me, ret => true, true);
            }

            public static Composite BuffSelf(string name, SimpleBooleanDelegate requirements)
            {
                return Buff(name, ret => StyxWoW.Me, requirements,true);
            }

            // Buff
            public static Composite Buff(string spellName, SimpleBooleanDelegate requirements)
            {
                return Buff(spellName, ret => StyxWoW.Me.CurrentTarget, requirements, false);
            }

            public static Composite Buff(string spellName, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements, bool checkHasMyAura, params string[] buffNames)
            {
                return
                    new Decorator(
                        ret => onUnit(ret) != null 
                            && !DoubleCastPreventionDict.ContainsKey(spellName) 
                            && buffNames.All(b => checkHasMyAura ? !onUnit(ret).HasMyAura(b) : !onUnit(ret).HasAura(b)),
                        new Sequence(Cast(spellName, requirements, onUnit),
                                     new DecoratorContinue(ret => SpellManager.Spells[spellName].CastTime > 0,
                                                           new Sequence(
                                                               new WaitContinue(1, ret => StyxWoW.Me.IsCasting,
                                                                                         new Action( ret => UpdateDoubleCastDict(spellName)))))));
            }


            #endregion

            #region Cast & Cast by ID & Cast on ground
            // Cast
            public static Composite Cast(string name)
            {
                return Cast(name, ret => true);
            }
            
            public static Composite Cast(string name, SimpleBooleanDelegate requirements)
            {
                return Cast(name, requirements, ret => StyxWoW.Me.CurrentTarget);
            }

            public static Composite Cast(string name, UnitSelectionDelegate onUnit)
            {
                return Cast(name, ret => true, onUnit);
            }

            public static Composite Cast(string spellName, SimpleBooleanDelegate requirement, UnitSelectionDelegate onTarget)
            {
                return new Decorator(ret =>
                                         {
                                             bool minReqs = (requirement != null && onTarget != null && requirement(ret) && onTarget(ret) != null && spellName != null);
                                             bool canCast = false, inRange = false;
                                             if (minReqs)
                                             {
                                                 //Log.Write("==== 1 " + spellName);
                                                 canCast = SpellManager.CanCast(spellName, onTarget(ret), false);
         
                                                 //Log.Write(Colours.InformativeColour,"=== [Debug " + spellName + "]  CanCast: " + canCast);

                                                 if (canCast)
                                                 {
                                                     //Log.Write("==== 2 inside can cast");
                                                     WoWUnit target = onTarget(ret);

                                                     if (target.IsMe){inRange = true;}
                                                     else
                                                     {
                                                         //Log.Write("==== 3");
                                                         WoWSpell spell;
                                                         //spell = SpellManager.Spells[spellName];
                                                         //if (spell != null)
                                                         if (SpellManager.Spells.TryGetValue(spellName, out spell))
                                                         {
                                                             //Log.Write("==== 4");
                                                             float minRange = spell.ActualMinRange(target);
                                                             float maxRange = spell.ActualMaxRange(target);
                                                             double targetDistance = target.Distance;

                                                             // RangeId 1 is "Self Only". This should make life easier for people to use self-buffs, or stuff like Starfall where you cast it as a pseudo-buff.
                                                             if (spell.IsSelfOnlySpell)inRange = true;
                                                                 // RangeId 2 is melee range. Huzzah :)
                                                             else if (spell.IsMeleeSpell)inRange = targetDistance < MeleeRange;
                                                             else inRange = targetDistance < maxRange &&targetDistance >(minRange == 0 ? minRange : minRange + 3);
                                                         }
                                                         else
                                                         {
                                                           //  Logging.Log.Write("==== TRY GET failed");
                                                         }
                                                     }
                                                 }
                                             }

                                             return minReqs && canCast && inRange;
                                         },
                                     new Sequence(
                                         new DecoratorContinue(ret => StyxWoW.Me.Mounted && !spellName.Contains("Aura") && !spellName.Contains("Aspect of") && !spellName.Contains("Presence") && !spellName.Contains("Stance"),
                                             Dismount("Casting spell")),
                                         new Action(ret =>
                                                        {
                                                            //Timers.Reset("Spell Cast Success");
                                                            Log.Write(string.Format("Casting {0} on {1}", spellName, onTarget(ret).SafeName()));
                                                            bool result = SpellManager.Cast(spellName, onTarget(ret));
                                                            if (result) Timers.Reset(spellName);
                                                        }),

                                         // changed to WaitContinue to avoid using Thread.Sleep as it freezes Wow momentarily because behaviors are now wrapped in framelock. /highvoltz
                                         new WaitContinue(TimeSpan.FromMilliseconds(500), ret =>
                                                                                              {
                                                                                                  WoWSpell spell;
                                                                                                  if (SpellManager.Spells.TryGetValue(spellName,out spell))
                                                                                                  {
                                                                                                      // This is here to prevent cancelling funneled and channeled spells right after the cast. /raphus
                                                                                                      if (spell.IsFunnel)return false;
                                                                                                  }
                                                                                                  return true;
                                                                                              },
                                                          new ActionAlwaysSucceed())));
            }

            // Cast by ID
            public static Composite Cast(int spellId)
            {
                return Cast(spellId, ret => true);
            }

            public static Composite Cast(int spellId, SimpleBooleanDelegate requirements)
            {
                return Cast(spellId, ret => StyxWoW.Me.CurrentTarget, requirements);
            }

            public static Composite Cast(int spellId, UnitSelectionDelegate onUnit)
            {
                return Cast(spellId, onUnit, ret => true);
            }

            public static Composite Cast(int spellId, UnitSelectionDelegate onUnit, SimpleBooleanDelegate requirements)
            {
                return
                    new Decorator( ret =>
                        requirements != null && requirements(ret) && onUnit != null && onUnit(ret) != null && SpellManager.CanCast(spellId, onUnit(ret), true), new Action(ret =>
                                                                                         {
                                                                                             Logging.Log.Write(string.Format( "Casting {0} on {1}", spellId, onUnit(ret). SafeName()));
                                                                                             SpellManager.Cast(spellId);
                                                                                         }));
            }

            // Cast on ground
            public static Composite CastOnGround(string spell, LocationRetriever onLocation)
            {
                return CastOnGround(spell, onLocation, ret => true);
            }

            public static Composite CastOnGround(string spell, LocationRetriever onLocation, SimpleBooleanDelegate requirements)
            {
                return CastOnGround(spell, onLocation, requirements, true);
            }

            public static Composite CastOnGround(string spell, LocationRetriever onLocation, SimpleBooleanDelegate requirements, bool waitForSpell)
            {
                return
                    new Decorator( ret => requirements(ret) && onLocation != null && SpellManager.CanCast(spell) && (StyxWoW.Me.Location.Distance(onLocation(ret)) <= SpellManager.Spells[spell].MaxRange || SpellManager.Spells[spell].MaxRange == 0),
                        new Sequence(
                            new Action(ret => Logging.Log.Write(string.Format("Casting {0} at location {1}", spell, onLocation(ret)))),
                            new Action(ret => SpellManager.Cast(spell)),

                            new DecoratorContinue(ctx => waitForSpell,
                                                  new WaitContinue(1, ret => StyxWoW.Me.CurrentPendingCursorSpell != null && StyxWoW.Me.CurrentPendingCursorSpell.Name == spell,
                                                                   new ActionAlwaysSucceed())),

                            new Action(ret => SpellManager.ClickRemoteLocation(onLocation(ret)))));
            }

            #endregion

            /*
            public static bool SpellOK(string spellName)
            {
                return SpellManager.HasSpell(spellName) && !SpellManager.Spells[spellName].Cooldown;
            }
             */

            public static bool OnCooldown(string spellName)
            {
                return HasSpell(spellName) && SpellManager.Spells[spellName].Cooldown;
            }

            public static bool OnCooldown(int spellID)
            {
                WoWSpell spell = null;

                if (HasSpell(spellID)) spell = WoWSpell.FromId(spellID);

                return spell != null && spell.Cooldown;
            }

            public static bool CanCast(int spellID)
            {
                WoWSpell spell = null;

                if (HasSpell(spellID)) spell = WoWSpell.FromId(spellID);

                return spell != null && CanCast(spell.Name);
            }

            public static bool CanCast() { return CanCast(null); }

            public static bool CanCast(string spellName)
            {
                //Logging.Log.Write("Me.IsCasting = " + Me.CastingSpell.Name);
                //Logging.Log.Write("Castingtime Left = " + Me.CurrentCastTimeLeft.TotalMilliseconds);
                //Logging.Log.Write("Channeled spell ID = " + Me.ChanneledCastingSpellId);
                //Logging.Log.Write("GCD = " + SpellManager.GlobalCooldown);
                //Logging.Log.Write("GCD Time left = " + SpellManager.GlobalCooldownLeft.TotalMilliseconds);


                if (SpellManager.GlobalCooldown && SpellManager.GlobalCooldownLeft.TotalMilliseconds > SpellClipMs)
                {
                    Logging.Log.Write(LogLevel.Diagnostic, "Spells.CanCast|GlobalCooldown|GCDLeft = " + SpellManager.GlobalCooldownLeft.TotalMilliseconds);
                    return false;
                }
                if (Me.IsCasting && Me.CurrentCastTimeLeft.TotalMilliseconds > SpellClipMs)
                {
                    //Logging.Log.Write(LogLevel.Diagnostic, "Spells.CanCast|Me.IsCasting|CastTimeLeft = " + Me.CurrentCastTimeLeft.TotalMilliseconds);
                    return false;
                }
                if (Me.ChanneledCastingSpellId != 0)
                {
                    //Logging.Log.Write(LogLevel.Diagnostic, "Spells.CanCast|Me.ChanneledCastingSpellId| = " + Me.ChanneledCastingSpellId);
                    return false;
                }

                if (spellName != null)
                {
                    if (!SpellManager.HasSpell(spellName)) return false;
                    if (SpellManager.Spells[spellName].Cooldown && SpellManager.Spells[spellName].CooldownTimeLeft.TotalMilliseconds > SpellClipMs) return false;
                }

                //return !SpellManager.GlobalCooldown && !Me.IsCasting && Me.ChanneledCastingSpellId == 0;
                return true;
            }

            public static double PowerCost(string spellName)
            {
                int powerCost = 9999;
                
                if (!SpellManager.HasSpell(spellName)) return powerCost;
                powerCost = SpellManager.Spells[spellName].PowerCost;

                return powerCost;
            }
        }
    }
}
